package la.bbs558.aop.valid;

import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.collect.Sets;
import la.bbs558.util.ApplicationUtils;
import la.bbs558.util.Objs;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @Classname ValidAop
 * @Description TODO
 * @Date 2023/11/23 16:38
 * @Created by weihanmin
 */
@Aspect
@Component
@Slf4j
@Order(-1)
public class ValidAop {

    @SneakyThrows
    @Before("@within(la.bbs558.aop.valid.Check) || @annotation(la.bbs558.aop.valid.Check)")
    public void before(JoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        Parameter[] parameters = method.getParameters();
        for (int i = 0, parametersLength = parameters.length; i < parametersLength; i++) {
            Parameter parameter = parameters[i];
            Object arg = args[i];
            for (Annotation annotation : parameter.getAnnotations()) {
                boolean filter = false;
                if (annotation instanceof Valid.filter){
                    for (Class<? extends ValidFilter> filterClass : ((Valid.filter) annotation).value()) {
                        Component component = filterClass.getAnnotation(Component.class);
                        ValidFilter validFilter = component != null ? ApplicationUtils.getBean(filterClass) : filterClass.newInstance();
                        filter = validFilter.filter(arg);
                    }
                }
                if(filter){
                    continue;
                }
                if(annotation instanceof Valid){
                    valid(arg, (Valid) annotation);
                }else if(annotation instanceof Valid.batch){
                    Valid[] valids = ((Valid.batch) annotation).value();
                    if(Objs.isValid(valids)){
                        for (Valid batchValid : valids) {
                            valid(arg, batchValid);
                        }
                    }
                }else if (annotation instanceof Valid.regex && arg instanceof CharSequence){
                    Valid.regex regex = (Valid.regex) annotation;
                    checkRegex(regex, (CharSequence) arg);
                }else if (annotation instanceof Valid.regex.batch && arg instanceof CharSequence){
                    Valid.regex.batch regexes = (Valid.regex.batch) annotation;
                    for (Valid.regex regex : regexes.value()) {
                        checkRegex(regex, (CharSequence) arg);
                    }

                }
            }
        }
    }

    private static void checkRegex(Valid.regex regex, CharSequence arg) {
        boolean matches = Pattern.matches(regex.value(), arg);
        if(!matches){
            throw new ValidException(regex.msg());
        }
    }

    @SneakyThrows
    private static void valid(Object object, Valid valid) {
        if(valid == null){
            return;
        }
        if (valid.enableCondition()) {
            Boolean check = valid.condition().check(object);
            if(!check){
                return;
            }
        }
        if(valid.isArray()){
            if(object instanceof Object[]){
                for (Object o : ((Object[]) object)) {
                    valid(o, valid);
                }
            }else if(object instanceof Collection){
                ((Collection<?>) object).forEach(item->{
                    valid(item, valid);
                });
            }
        }
        boolean isJson = valid.isJsonString();
        String msg = valid.msg();
        if(isJson){
            if(JSONUtil.isTypeJSON(object.toString())){
                object = JSON.parseObject(object.toString(),valid.jsonToClass());
            }else {
                throw new ValidException(msg);
            }
        }
        ValidEnum validEnum = valid.value();
        validEnum.check(object, msg);
        String[] group = valid.group();
        Set<String> groupSet = Arrays.stream(group).collect(Collectors.toSet());
        boolean groupNotEmpty = CollectionUtils.isNotEmpty(groupSet);
        if(checkValueIsBaseType(object, valid)){
            Class<?> clazz = object.getClass();
            Field[] fields = clazz.getDeclaredFields();
            for (Field field : fields) {
                Valid fieldValid = field.getAnnotation(Valid.class);
                if(Objs.isInvalid(fieldValid)){
                    continue;
                }
                String[] fieldGroup = fieldValid.parentsGroup();
                if(groupNotEmpty && Objs.isValid(fieldGroup)){
                    Set<String> fieldGroupSet = Arrays.stream(fieldGroup).collect(Collectors.toSet());
                    Sets.SetView<String> intersection = Sets.intersection(groupSet, fieldGroupSet);
                    if(Objs.isInvalid(intersection)){
                        continue;
                    }
                }
                field.setAccessible(true);
                Object fieldObj = field.get(object);
                valid(fieldObj,fieldValid);
            }
        }
    }

    private static boolean checkValueIsBaseType(Object object, Valid valid) {
        return valid.internal()
                && !Objs.isBasicType(object)
                && !(object instanceof CharSequence)
                && !(object instanceof Date)
                ;
    }


}
